/*-------------------------------------------------------------------------
 *
 * pquery.c
 *	  POSTGRES 进程查询命令代码
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/tcop/pquery.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <limits.h>

#include "access/xact.h"
#include "commands/prepare.h"
#include "executor/tstoreReceiver.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"


/*
 * ActivePortal 是当前正在执行的 Portal（如果有多个，则为最紧密嵌套的）。
 */
Portal		ActivePortal = NULL;


static void fc_ProcessQuery(PlannedStmt *fc_plan,
						 const char *fc_sourceText,
						 ParamListInfo fc_params,
						 QueryEnvironment *fc_queryEnv,
						 DestReceiver *fc_dest,
						 QueryCompletion *fc_qc);
static void fc_FillPortalStore(Portal fc_portal, bool fc_isTopLevel);
static uint64 fc_RunFromStore(Portal fc_portal, ScanDirection fc_direction, uint64 fc_count,
						   DestReceiver *fc_dest);
static uint64 fc_PortalRunSelect(Portal fc_portal, bool fc_forward, long fc_count,
							  DestReceiver *fc_dest);
static void fc_PortalRunUtility(Portal fc_portal, PlannedStmt *fc_pstmt,
							 bool fc_isTopLevel, bool fc_setHoldSnapshot,
							 DestReceiver *fc_dest, QueryCompletion *fc_qc);
static void fc_PortalRunMulti(Portal fc_portal,
						   bool fc_isTopLevel, bool fc_setHoldSnapshot,
						   DestReceiver *fc_dest, DestReceiver *fc_altdest,
						   QueryCompletion *fc_qc);
static uint64 fc_DoPortalRunFetch(Portal fc_portal,
							   FetchDirection fc_fdirection,
							   long fc_count,
							   DestReceiver *fc_dest);
static void fc_DoPortalRewind(Portal fc_portal);


/*
 * CreateQueryDesc
 */
QueryDesc *
CreateQueryDesc(PlannedStmt *fc_plannedstmt,
				const char *fc_sourceText,
				Snapshot fc_snapshot,
				Snapshot fc_crosscheck_snapshot,
				DestReceiver *fc_dest,
				ParamListInfo fc_params,
				QueryEnvironment *fc_queryEnv,
				int fc_instrument_options)
{
	QueryDesc  *fc_qd = (QueryDesc *) palloc(sizeof(QueryDesc));

	fc_qd->operation = fc_plannedstmt->commandType;	/* 操作 */
	fc_qd->plannedstmt = fc_plannedstmt;	/* plan */
	fc_qd->sourceText = fc_sourceText;	/* 查询文本 */
	fc_qd->snapshot = RegisterSnapshot(fc_snapshot);	/* 快照 */
	/* RI 检查快照 */
	fc_qd->crosscheck_snapshot = RegisterSnapshot(fc_crosscheck_snapshot);
	fc_qd->dest = fc_dest;			/* 输出目标 */
	fc_qd->params = fc_params;		/* 传入查询的参数值 */
	fc_qd->queryEnv = fc_queryEnv;
	fc_qd->instrument_options = fc_instrument_options;	/* 需要仪器监测吗？ */

	/* 在 ExecutorStart 设置之前将这些字段设为 null */
	fc_qd->tupDesc = NULL;
	fc_qd->estate = NULL;
	fc_qd->planstate = NULL;
	fc_qd->totaltime = NULL;

	/* 尚未执行 */
	fc_qd->already_executed = false;

	return fc_qd;
}

/*
 * FreeQueryDesc
 */
void FreeQueryDesc(QueryDesc *fc_qdesc)
{
	/* 不能是一个实时查询 */
	Assert(fc_qdesc->estate == NULL);

	/* 忘记我们的快照 */
	UnregisterSnapshot(fc_qdesc->snapshot);
	UnregisterSnapshot(fc_qdesc->crosscheck_snapshot);

	/* 只有 QueryDesc 本身需要释放 */
	pfree(fc_qdesc);
}


/*
 * ProcessQuery
 *		在 PORTAL_MULTI_QUERY、
 *		PORTAL_ONE_RETURNING 或 PORTAL_ONE_MOD_WITH portal 中执行单个可规划查询
 *
 *	plan: 查询的计划树
 *	sourceText: 查询的源文本
 *	params: 所需的任何参数
 *	dest: 发送结果的地方
 *	qc: 存储命令完成状态数据的位置。
 *
 * 如果调用者不想要状态字符串，qc 可以为 NULL。
 *
 * 必须在一个将在错误时重置或删除的内存上下文中调用；否则，执行器的内存使用将泄漏。
 */
static void fc_ProcessQuery(PlannedStmt *fc_plan,
			 const char *fc_sourceText,
			 ParamListInfo fc_params,
			 QueryEnvironment *fc_queryEnv,
			 DestReceiver *fc_dest,
			 QueryCompletion *fc_qc)
{
	QueryDesc  *fc_queryDesc;

	/*
	 * 创建 QueryDesc 对象
	 */
	fc_queryDesc = CreateQueryDesc(fc_plan, fc_sourceText,
								GetActiveSnapshot(), InvalidSnapshot,
								fc_dest, fc_params, fc_queryEnv, 0);

	/*
	 * 调用 ExecutorStart 准备执行计划
	 */
	ExecutorStart(fc_queryDesc, 0);

	/*
	 * 完成计划的执行。
	 */
	ExecutorRun(fc_queryDesc, ForwardScanDirection, 0L, true);

	/*
	 * 如果调用者想要，构建命令完成状态数据。
	 */
	if (fc_qc)
	{
		switch (fc_queryDesc->operation)
		{
			case CMD_SELECT:
				SetQueryCompletion(fc_qc, CMDTAG_SELECT, fc_queryDesc->estate->es_processed);
				break;
			case CMD_INSERT:
				SetQueryCompletion(fc_qc, CMDTAG_INSERT, fc_queryDesc->estate->es_processed);
				break;
			case CMD_UPDATE:
				SetQueryCompletion(fc_qc, CMDTAG_UPDATE, fc_queryDesc->estate->es_processed);
				break;
			case CMD_DELETE:
				SetQueryCompletion(fc_qc, CMDTAG_DELETE, fc_queryDesc->estate->es_processed);
				break;
			case CMD_MERGE:
				SetQueryCompletion(fc_qc, CMDTAG_MERGE, fc_queryDesc->estate->es_processed);
				break;
			default:
				SetQueryCompletion(fc_qc, CMDTAG_UNKNOWN, fc_queryDesc->estate->es_processed);
				break;
		}
	}

	/*
	 * 现在，我们关闭所有扫描并释放分配的资源。
	 */
	ExecutorFinish(fc_queryDesc);
	ExecutorEnd(fc_queryDesc);

	FreeQueryDesc(fc_queryDesc);
}

/*
 * ChoosePortalStrategy
 *		选择给定意图语句列表的门户执行策略。
 *
 * 列表元素可以是查询或计划语句。
 * 这比门户所需的更一般，但 plancache.c 也使用此内容。
 *
 * 请参见 portal.h 中的注释。
 */
PortalStrategy
ChoosePortalStrategy(List *fc_stmts)
{
	int			fc_nSetTag;
	ListCell   *fc_lc;

	/*
	 * PORTAL_ONE_SELECT 和 PORTAL_UTIL_SELECT 只需考虑
	 * 单语句情况，因为没有重写规则可以向 SELECT 或实用命令添加
	 * 辅助查询。PORTAL_ONE_MOD_WITH
	 * 同样只允许一个顶级语句。
	 */
	if (list_length(fc_stmts) == 1)
	{
		Node	   *fc_stmt = (Node *) linitial(fc_stmts);

		if (IsA(fc_stmt, Query))
		{
			Query	   *fc_query = (Query *) fc_stmt;

			if (fc_query->canSetTag)
			{
				if (fc_query->commandType == CMD_SELECT)
				{
					if (fc_query->hasModifyingCTE)
						return PORTAL_ONE_MOD_WITH;
					else
						return PORTAL_ONE_SELECT;
				}
				if (fc_query->commandType == CMD_UTILITY)
				{
					if (UtilityReturnsTuples(fc_query->utilityStmt))
						return PORTAL_UTIL_SELECT;
					/* 它不能是 ONE_RETURNING，所以放弃 */
					return PORTAL_MULTI_QUERY;
				}
			}
		}
		else if (IsA(fc_stmt, PlannedStmt))
		{
			PlannedStmt *fc_pstmt = (PlannedStmt *) fc_stmt;

			if (fc_pstmt->canSetTag)
			{
				if (fc_pstmt->commandType == CMD_SELECT)
				{
					if (fc_pstmt->hasModifyingCTE)
						return PORTAL_ONE_MOD_WITH;
					else
						return PORTAL_ONE_SELECT;
				}
				if (fc_pstmt->commandType == CMD_UTILITY)
				{
					if (UtilityReturnsTuples(fc_pstmt->utilityStmt))
						return PORTAL_UTIL_SELECT;
					/* 它不能是 ONE_RETURNING，所以放弃 */
					return PORTAL_MULTI_QUERY;
				}
			}
		}
		else
			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(fc_stmt));
	}

	/*
	 * PORTAL_ONE_RETURNING 必须允许重写添加的辅助查询。
	 * 如果恰好有一个 canSetTag 查询并且有一个 RETURNING 列表，
	 * 则选择 PORTAL_ONE_RETURNING。
	 */
	fc_nSetTag = 0;
	foreach(fc_lc, fc_stmts)
	{
		Node	   *fc_stmt = (Node *) lfirst(fc_lc);

		if (IsA(fc_stmt, Query))
		{
			Query	   *fc_query = (Query *) fc_stmt;

			if (fc_query->canSetTag)
			{
				if (++fc_nSetTag > 1)
					return PORTAL_MULTI_QUERY;	/* 没必要再进一步查看 */
				if (fc_query->commandType == CMD_UTILITY ||
					fc_query->returningList == NIL)
					return PORTAL_MULTI_QUERY;	/* 没必要再进一步查看 */
			}
		}
		else if (IsA(fc_stmt, PlannedStmt))
		{
			PlannedStmt *fc_pstmt = (PlannedStmt *) fc_stmt;

			if (fc_pstmt->canSetTag)
			{
				if (++fc_nSetTag > 1)
					return PORTAL_MULTI_QUERY;	/* 没必要再进一步查看 */
				if (fc_pstmt->commandType == CMD_UTILITY ||
					!fc_pstmt->hasReturning)
					return PORTAL_MULTI_QUERY;	/* 没必要再进一步查看 */
			}
		}
		else
			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(fc_stmt));
	}
	if (fc_nSetTag == 1)
		return PORTAL_ONE_RETURNING;

	/* 否则，这是一般情况... */
	return PORTAL_MULTI_QUERY;
}

/*
 * FetchPortalTargetList
 *		给定一个返回元组的门户，提取查询目标列表。
 *		如果门户没有可确定的目标列表，则返回 NIL。
 *
 * 注意：请勿修改结果。
 */
List * FetchPortalTargetList(Portal fc_portal)
{
	/* 如果我们已经确定它不返回元组，就没有必要查看 */
	if (fc_portal->strategy == PORTAL_MULTI_QUERY)
		return NIL;
	/* 获取主要语句并找出它返回什么 */
	return FetchStatementTargetList((Node *) PortalGetPrimaryStmt(fc_portal));
}

/*
 * FetchStatementTargetList
 *		给定一个返回元组的语句，提取查询目标列表。
 *		如果语句没有可确定的目标列表，则返回 NIL。
 *
 * 这可以应用于查询或计划语句。
 * 这比门户所需的更一般，但 plancache.c 也使用此内容。
 *
 * 注意：请勿修改结果。
 *
 * XXX 请小心与 UtilityReturnsTuples 保持同步。
 */
List * FetchStatementTargetList(Node *fc_stmt)
{
	if (fc_stmt == NULL)
		return NIL;
	if (IsA(fc_stmt, Query))
	{
		Query	   *fc_query = (Query *) fc_stmt;

		if (fc_query->commandType == CMD_UTILITY)
		{
			/* 转移注意力到实用语句 */
			fc_stmt = fc_query->utilityStmt;
		}
		else
		{
			if (fc_query->commandType == CMD_SELECT)
				return fc_query->targetList;
			if (fc_query->returningList)
				return fc_query->returningList;
			return NIL;
		}
	}
	if (IsA(fc_stmt, PlannedStmt))
	{
		PlannedStmt *fc_pstmt = (PlannedStmt *) fc_stmt;

		if (fc_pstmt->commandType == CMD_UTILITY)
		{
			/* 转移注意力到实用语句 */
			fc_stmt = fc_pstmt->utilityStmt;
		}
		else
		{
			if (fc_pstmt->commandType == CMD_SELECT)
				return fc_pstmt->planTree->targetlist;
			if (fc_pstmt->hasReturning)
				return fc_pstmt->planTree->targetlist;
			return NIL;
		}
	}
	if (IsA(fc_stmt, FetchStmt))
	{
		FetchStmt  *fc_fstmt = (FetchStmt *) fc_stmt;
		Portal		fc_subportal;

		Assert(!fc_fstmt->ismove);
		fc_subportal = GetPortalByName(fc_fstmt->portalname);
		Assert(PortalIsValid(fc_subportal));
		return FetchPortalTargetList(fc_subportal);
	}
	if (IsA(fc_stmt, ExecuteStmt))
	{
		ExecuteStmt *fc_estmt = (ExecuteStmt *) fc_stmt;
		PreparedStatement *fc_entry;

		fc_entry = FetchPreparedStatement(fc_estmt->name, true);
		return FetchPreparedStatementTargetList(fc_entry);
	}
	return NIL;
}

/*
 * PortalStart
 *		准备门户以进行执行。
 *
 * 调用者必须已经创建了门户，完成了 PortalDefineQuery()，
 * 并在需要时调整门户选项。
 *
 * 如果查询需要参数，则必须通过 "params" 传入
 * （调用者负责提供适当的生命周期）。
 *
 * 调用者还可以提供初始的 "eflags" 集，以传递到
 * ExecutorStart （但请注意，这些可以被内部修改，并且它们
 * 目前仅对 PORTAL_ONE_SELECT 门户生效）。大多数调用者
 * 应该简单地传递零。
 *
 * 调用者可以选择传递一个快照以供使用；传递 InvalidSnapshot
 * 以获取设置新快照的正常行为。该参数
 * 当前对非 PORTAL_ONE_SELECT 门户无效（它仅用于游标）。
 *
 * 返回时，门户准备好接受 PortalRun() 调用，结果
 * tupdesc（如果有）已知。
 */
void PortalStart(Portal fc_portal, ParamListInfo fc_params,
			int fc_eflags, Snapshot fc_snapshot)
{
	Portal		fc_saveActivePortal;
	ResourceOwner fc_saveResourceOwner;
	MemoryContext fc_savePortalContext;
	MemoryContext fc_oldContext;
	QueryDesc  *fc_queryDesc;
	int			fc_myeflags;

	AssertArg(PortalIsValid(fc_portal));
	AssertState(fc_portal->status == PORTAL_DEFINED);

	/*
	 * 设置全局门户上下文指针。
	 */
	fc_saveActivePortal = ActivePortal;
	fc_saveResourceOwner = CurrentResourceOwner;
	fc_savePortalContext = PortalContext;
	PG_TRY();
	{
		ActivePortal = fc_portal;
		if (fc_portal->resowner)
			CurrentResourceOwner = fc_portal->resowner;
		PortalContext = fc_portal->portalContext;

		fc_oldContext = MemoryContextSwitchTo(PortalContext);

		/* 如果有，必须记住门户参数列表 */
		fc_portal->portalParams = fc_params;

		/*
		 * 确定门户执行策略
		 */
		fc_portal->strategy = ChoosePortalStrategy(fc_portal->stmts);

		/*
		 * 根据策略启动它
		 */
		switch (fc_portal->strategy)
		{
			case PORTAL_ONE_SELECT:

				/* 必须在启动执行器之前设置快照。 */
				if (fc_snapshot)
					PushActiveSnapshot(fc_snapshot);
				else
					PushActiveSnapshot(GetTransactionSnapshot());

				/*
				 * 我们可以记住快照在 portal->portalSnapshot 中，
				 * 但目前似乎没有必要，因为该代码路径
				 * 不能用于非原子执行。因此，没有
				 * 提交/中止可能破坏快照。由于
				 * 我们不这样做，因此也没有必要强制
				 * 快照使用非默认嵌套级别。
				 */

				/*
				 * 在门户的上下文中创建 QueryDesc；暂时将
				 * 目标设置为 DestNone。
				 */
				fc_queryDesc = CreateQueryDesc(linitial_node(PlannedStmt, fc_portal->stmts),
											fc_portal->sourceText,
											GetActiveSnapshot(),
											InvalidSnapshot,
											None_Receiver,
											fc_params,
											fc_portal->queryEnv,
											0);

				/*
				 * 如果是可滚动游标，执行器需要支持
				 * REWIND 和向后扫描，以及调用者
				 * 可能请求的其他内容。
				 */
				if (fc_portal->cursorOptions & CURSOR_OPT_SCROLL)
					fc_myeflags = fc_eflags | EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD;
				else
					fc_myeflags = fc_eflags;

				/*
				 * 调用 ExecutorStart 准备执行计划
				 */
				ExecutorStart(fc_queryDesc, fc_myeflags);

				/*
				 * 这告诉 PortalCleanup 关闭执行器
				 */
				fc_portal->queryDesc = fc_queryDesc;

				/*
				 * 记住元组描述符（由 ExecutorStart 计算）
				 */
				fc_portal->tupDesc = fc_queryDesc->tupDesc;

				/*
				 * 重置游标位置数据为“查询开始”
				 */
				fc_portal->atStart = true;
				fc_portal->atEnd = false;	/* 允许获取 */
				fc_portal->portalPos = 0;

				PopActiveSnapshot();
				break;

			case PORTAL_ONE_RETURNING:
			case PORTAL_ONE_MOD_WITH:

				/*
				 * 我们不会在被告知运行门户之前启动执行器。
				 * 我们确实需要设置结果 tupdesc。
				 */
				{
					PlannedStmt *fc_pstmt;

					fc_pstmt = PortalGetPrimaryStmt(fc_portal);
					fc_portal->tupDesc =
						ExecCleanTypeFromTL(fc_pstmt->planTree->targetlist);
				}

				/*
				 * 重置游标位置数据为“查询开始”
				 */
				fc_portal->atStart = true;
				fc_portal->atEnd = false;	/* 允许获取 */
				fc_portal->portalPos = 0;
				break;

			case PORTAL_UTIL_SELECT:

				/*
				 * 我们在这里不设置快照，因为 PortalRunUtility 将
				 * 如果需要处理它。
				 */
				{
					PlannedStmt *fc_pstmt = PortalGetPrimaryStmt(fc_portal);

					Assert(fc_pstmt->commandType == CMD_UTILITY);
					fc_portal->tupDesc = UtilityTupleDescriptor(fc_pstmt->utilityStmt);
				}

				/*
				 * 重置游标位置数据为“查询开始”
				 */
				fc_portal->atStart = true;
				fc_portal->atEnd = false;	/* 允许获取 */
				fc_portal->portalPos = 0;
				break;

			case PORTAL_MULTI_QUERY:
				/* 现在无需做任何事 */
				fc_portal->tupDesc = NULL;
				break;
		}
	}
	PG_CATCH();
	{
		/* 执行门户时发生未捕获的错误：将其标记为已死 */
		MarkPortalFailed(fc_portal);

		/* 恢复全局变量并传播错误 */
		ActivePortal = fc_saveActivePortal;
		CurrentResourceOwner = fc_saveResourceOwner;
		PortalContext = fc_savePortalContext;

		PG_RE_THROW();
	}
	PG_END_TRY();

	MemoryContextSwitchTo(fc_oldContext);

	ActivePortal = fc_saveActivePortal;
	CurrentResourceOwner = fc_saveResourceOwner;
	PortalContext = fc_savePortalContext;

	fc_portal->status = PORTAL_READY;
}

/*
 * PortalSetResultFormat
 *		选择门户输出的格式代码。
 *
 * 这必须在 PortalStart 之后运行，以便门户可以通过
 * DestRemote 或 DestRemoteExecute 目标读取。对于其他目标类型
 * 目前不需要。
 *
 * formats[] 是客户端格式请求，符合 Bind 消息约定。
 */
void PortalSetResultFormat(Portal fc_portal, int fc_nFormats, int16 *fc_formats)
{
	int			fc_natts;
	int			fc_i;

	/* 如果门户不会返回元组，则无需执行任何操作 */
	if (fc_portal->tupDesc == NULL)
		return;
	fc_natts = fc_portal->tupDesc->natts;
	fc_portal->formats = (int16 *)
		MemoryContextAlloc(fc_portal->portalContext,
						   fc_natts * sizeof(int16));
	if (fc_nFormats > 1)
	{
		/* 为每列指定的格式 */
		if (fc_nFormats != fc_natts)
			ereport(ERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("bind message has %d result formats but query has %d columns",
							fc_nFormats, fc_natts)));
		memcpy(fc_portal->formats, fc_formats, fc_natts * sizeof(int16));
	}
	else if (fc_nFormats > 0)
	{
		/* 指定了单一格式，适用于所有列 */
		int16		fc_format1 = fc_formats[0];

		for (fc_i = 0; fc_i < fc_natts; fc_i++)
			fc_portal->formats[fc_i] = fc_format1;
	}
	else
	{
		/* 使用默认格式用于所有列 */
		for (fc_i = 0; fc_i < fc_natts; fc_i++)
			fc_portal->formats[fc_i] = 0;
	}
}

/*
 * PortalRun
 *		运行一个门户的查询或查询集。
 *
 * count <= 0 被解释为不进行操作：目的地启动并关闭，但没有其他事情发生。此外，count == FETCH_ALL 被
 * 解释为“所有行”。请注意，在多查询情况下，count 被忽略，我们总是运行门户直到完成。
 *
 * isTopLevel：如果查询在后端“顶层”执行（即，直接来自客户端命令消息），则为 true。
 *
 * run_once：被忽略，仅存在以避免在稳定分支中造成 API 中断。
 *
 * dest：输出主查询的去向（canSetTag）
 *
 * altdest：非主查询的输出去向
 *
 * qc：存储命令完成状态数据的地方。
 *		如果调用者不想要状态数据，可以是 NULL。
 *
 * 如果门户的执行完成，则返回 true；如果由于 count 参数耗尽而被暂停，则返回 false。
 */
bool PortalRun(Portal fc_portal, long fc_count, bool fc_isTopLevel, bool fc_run_once,
		  DestReceiver *fc_dest, DestReceiver *fc_altdest,
		  QueryCompletion *fc_qc)
{
	bool		fc_result;
	uint64		fc_nprocessed;
	ResourceOwner fc_saveTopTransactionResourceOwner;
	MemoryContext fc_saveTopTransactionContext;
	Portal		fc_saveActivePortal;
	ResourceOwner fc_saveResourceOwner;
	MemoryContext fc_savePortalContext;
	MemoryContext fc_saveMemoryContext;

	AssertArg(PortalIsValid(fc_portal));

	TRACE_POSTGRESQL_QUERY_EXECUTE_START();

	/* 初始化为空的完成数据 */
	if (fc_qc)
		InitializeQueryCompletion(fc_qc);

	if (log_executor_stats && fc_portal->strategy != PORTAL_MULTI_QUERY)
	{
		elog(DEBUG3, "PortalRun");
		/* PORTAL_MULTI_QUERY 每个查询记录自己的统计数据 */
		ResetUsage();
	}

	/*
	 * 检查门户使用不当，并标记门户为活动。
	 */
	MarkPortalActive(fc_portal);

	/*
	 * 设置全局门户上下文指针。
	 *
	 * 我们在这里必须进行特别处理，以支持 VACUUM 和 CLUSTER 等实用命令，这些命令内部启动并提交事务。
	 * 当我们被调用执行这样的命令时，CurrentResourceOwner 将
	 * 指向 TopTransactionResourceOwner --- 它将在内部提交和
	 * 重启的过程中被销毁和替换。所以我们需要准备恢复它，使其指向
	 * 退出时的 TopTransactionResourceOwner。 （这不是很好看？内部启动新的
	 * 事务的想法不好。）
	 * CurrentMemoryContext 也有类似的问题，但我们在这里保存的其他指针
	 * 将是 NULL 或指向更长寿命的对象。
	 */
	fc_saveTopTransactionResourceOwner = TopTransactionResourceOwner;
	fc_saveTopTransactionContext = TopTransactionContext;
	fc_saveActivePortal = ActivePortal;
	fc_saveResourceOwner = CurrentResourceOwner;
	fc_savePortalContext = PortalContext;
	fc_saveMemoryContext = CurrentMemoryContext;
	PG_TRY();
	{
		ActivePortal = fc_portal;
		if (fc_portal->resowner)
			CurrentResourceOwner = fc_portal->resowner;
		PortalContext = fc_portal->portalContext;

		MemoryContextSwitchTo(PortalContext);

		switch (fc_portal->strategy)
		{
			case PORTAL_ONE_SELECT:
			case PORTAL_ONE_RETURNING:
			case PORTAL_ONE_MOD_WITH:
			case PORTAL_UTIL_SELECT:

				/*
				 * 如果我们还没有运行该命令，则执行之，将其
				 * 结果存储在门户的 tuplestore 中。但对于 PORTAL_ONE_SELECT 情况，我们不这样做。
				 */
				if (fc_portal->strategy != PORTAL_ONE_SELECT && !fc_portal->holdStore)
					fc_FillPortalStore(fc_portal, fc_isTopLevel);

				/*
				 * 现在获取所需部分的结果。
				 */
				fc_nprocessed = fc_PortalRunSelect(fc_portal, true, fc_count, fc_dest);

				/*
				 * 如果门户结果包含命令标签，并且调用者
				 * 给了我们一个存储它的指针，则复制并更新
				 * 行数。
				 */
				if (fc_qc && fc_portal->qc.commandTag != CMDTAG_UNKNOWN)
				{
					CopyQueryCompletion(fc_qc, &fc_portal->qc);
					fc_qc->nprocessed = fc_nprocessed;
				}

				/* 将门户标记为不活动 */
				fc_portal->status = PORTAL_READY;

				/*
				 * 由于这是向前获取，如果 atEnd 现在为 true，则表示 DONE。
				 */
				fc_result = fc_portal->atEnd;
				break;

			case PORTAL_MULTI_QUERY:
				fc_PortalRunMulti(fc_portal, fc_isTopLevel, false,
							   fc_dest, fc_altdest, fc_qc);

				/* 防止门户命令被重新执行 */
				MarkPortalDone(fc_portal);

				/* 始终在 RunMulti 结束时完成 */
				fc_result = true;
				break;

			default:
				elog(ERROR, "unrecognized portal strategy: %d",
					 (int) fc_portal->strategy);
				fc_result = false; /* 保持编译器安静 */
				break;
		}
	}
	PG_CATCH();
	{
		/* 执行门户时发生未捕获的错误：将其标记为已死 */
		MarkPortalFailed(fc_portal);

		/* 恢复全局变量并传播错误 */
		if (fc_saveMemoryContext == fc_saveTopTransactionContext)
			MemoryContextSwitchTo(TopTransactionContext);
		else
			MemoryContextSwitchTo(fc_saveMemoryContext);
		ActivePortal = fc_saveActivePortal;
		if (fc_saveResourceOwner == fc_saveTopTransactionResourceOwner)
			CurrentResourceOwner = TopTransactionResourceOwner;
		else
			CurrentResourceOwner = fc_saveResourceOwner;
		PortalContext = fc_savePortalContext;

		PG_RE_THROW();
	}
	PG_END_TRY();

	if (fc_saveMemoryContext == fc_saveTopTransactionContext)
		MemoryContextSwitchTo(TopTransactionContext);
	else
		MemoryContextSwitchTo(fc_saveMemoryContext);
	ActivePortal = fc_saveActivePortal;
	if (fc_saveResourceOwner == fc_saveTopTransactionResourceOwner)
		CurrentResourceOwner = TopTransactionResourceOwner;
	else
		CurrentResourceOwner = fc_saveResourceOwner;
	PortalContext = fc_savePortalContext;

	if (log_executor_stats && fc_portal->strategy != PORTAL_MULTI_QUERY)
		ShowUsage("EXECUTOR STATISTICS");

	TRACE_POSTGRESQL_QUERY_EXECUTE_DONE();

	return fc_result;
}

/*
 * PortalRunSelect
 *		以 PORTAL_ONE_SELECT 模式执行门户的查询，同时
 *		当从已完成的 holdStore 中获取时适用于 PORTAL_ONE_RETURNING、
 *		PORTAL_ONE_MOD_WITH 和 PORTAL_UTIL_SELECT 情况。
 *
 * 这处理简单的 N 行前进或后退情况。有关更复杂的对门户的非顺序访问，请参见 PortalRunFetch。
 *
 * count <= 0 被解释为不进行操作：目的地启动并关闭，但没有其他事情发生。此外，count == FETCH_ALL 被
 * 解释为“所有行”。（参见 FetchStmt.howMany）
 *
 * 调用者必须已经验证门户并进行适当的设置（参见 PortalRun）。
 *
 * 返回处理的行数（适合用于结果标签）
 */
static uint64 fc_PortalRunSelect(Portal fc_portal,
				bool fc_forward,
				long fc_count,
				DestReceiver *fc_dest)
{
	QueryDesc  *fc_queryDesc;
	ScanDirection fc_direction;
	uint64		fc_nprocessed;

	/*
	 * 注意：如果我们从持有的光标或完成的实用查询中获取，则 queryDesc 将为 NULL；
	 * 在该路径中不能使用它。
	 */
	fc_queryDesc = fc_portal->queryDesc;

	/* 如果我们既没有准备好的查询也没有持有的数据，调用者就是 messed up。 */
	Assert(fc_queryDesc || fc_portal->holdStore);

	/*
	 * 强制将 queryDesc 目标设置为正确的值。这支持
	 * MOVE，例如，将 dest = DestNone 传递给它。这可以被
	 * 更改，只要我们在每次获取时执行此操作。（执行器不得
	 * 假设 dest 从不改变。）
	 */
	if (fc_queryDesc)
		fc_queryDesc->dest = fc_dest;

	/*
	 * 确定要前进的方向，并检查我们是否已经
	 * 到达该方向可用元组的末尾。如果是，则将方向设置为 NoMovement，以避免尝试获取任何元组。
	 * （此检查存在是因为并非所有计划节点类型在已返回 NULL
	 * 一次后再次被调用时都能稳定。）然后调用执行器（我们必须不跳过这一步，因为目的地需要看到即使
	 * 没有可用元组的设置和关闭）。最后，根据检索到的元组数量更新门户位置状态。
	 */
	if (fc_forward)
	{
		if (fc_portal->atEnd || fc_count <= 0)
		{
			fc_direction = NoMovementScanDirection;
			fc_count = 0;			/* 不要将负计数传递给执行器 */
		}
		else
			fc_direction = ForwardScanDirection;

		/* 在执行器中，零计数处理所有行 */
		if (fc_count == FETCH_ALL)
			fc_count = 0;

		if (fc_portal->holdStore)
			fc_nprocessed = fc_RunFromStore(fc_portal, fc_direction, (uint64) fc_count, fc_dest);
		else
		{
			PushActiveSnapshot(fc_queryDesc->snapshot);
			ExecutorRun(fc_queryDesc, fc_direction, (uint64) fc_count,
						false);
			fc_nprocessed = fc_queryDesc->estate->es_processed;
			PopActiveSnapshot();
		}

		if (!ScanDirectionIsNoMovement(fc_direction))
		{
			if (fc_nprocessed > 0)
				fc_portal->atStart = false;	/* 现在可以向后移动 */
			if (fc_count == 0 || fc_nprocessed < (uint64) fc_count)
				fc_portal->atEnd = true;	/* 我们已获取所有行 */
			fc_portal->portalPos += fc_nprocessed;
		}
	}
	else
	{
		if (fc_portal->cursorOptions & CURSOR_OPT_NO_SCROLL)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					 errmsg("cursor can only scan forward"),
					 errhint("Declare it with SCROLL option to enable backward scan.")));

		if (fc_portal->atStart || fc_count <= 0)
		{
			fc_direction = NoMovementScanDirection;
			fc_count = 0;			/* 不要将负计数传递给执行器 */
		}
		else
			fc_direction = BackwardScanDirection;

		/* 在执行器中，零计数处理所有行 */
		if (fc_count == FETCH_ALL)
			fc_count = 0;

		if (fc_portal->holdStore)
			fc_nprocessed = fc_RunFromStore(fc_portal, fc_direction, (uint64) fc_count, fc_dest);
		else
		{
			PushActiveSnapshot(fc_queryDesc->snapshot);
			ExecutorRun(fc_queryDesc, fc_direction, (uint64) fc_count,
						false);
			fc_nprocessed = fc_queryDesc->estate->es_processed;
			PopActiveSnapshot();
		}

		if (!ScanDirectionIsNoMovement(fc_direction))
		{
			if (fc_nprocessed > 0 && fc_portal->atEnd)
			{
				fc_portal->atEnd = false;	/* 现在可以向前移动 */
				fc_portal->portalPos++;	/* 针对端点情况进行调整 */
			}
			if (fc_count == 0 || fc_nprocessed < (uint64) fc_count)
			{
				fc_portal->atStart = true; /* 我们已获取所有行 */
				fc_portal->portalPos = 0;
			}
			else
			{
				fc_portal->portalPos -= fc_nprocessed;
			}
		}
	}

	return fc_nprocessed;
}

/*
 * FillPortalStore
 *		运行查询并将结果元组加载到门户的元组存储中。
 *
 * 仅在 PORTAL_ONE_RETURNING、PORTAL_ONE_MOD_WITH 和
 * PORTAL_UTIL_SELECT 情况下使用。
 */
static void fc_FillPortalStore(Portal fc_portal, bool fc_isTopLevel)
{
	DestReceiver *fc_treceiver;
	QueryCompletion fc_qc;

	InitializeQueryCompletion(&fc_qc);
	PortalCreateHoldStore(fc_portal);
	fc_treceiver = CreateDestReceiver(DestTuplestore);
	SetTuplestoreDestReceiverParams(fc_treceiver,
									fc_portal->holdStore,
									fc_portal->holdContext,
									false,
									NULL,
									NULL);

	switch (fc_portal->strategy)
	{
		case PORTAL_ONE_RETURNING:
		case PORTAL_ONE_MOD_WITH:

			
/*
			 * 像默认的 PORTAL_MULTI_QUERY 情况一样运行门户以完成，但将主查询的输出发送到
			 * tuplestore。辅助查询的输出将被丢弃。将门户的 holdSnapshot 设置为使用的快照（或其副本）。
			 */
			fc_PortalRunMulti(fc_portal, fc_isTopLevel, true,
						   fc_treceiver, None_Receiver, &fc_qc);
			break;

		case PORTAL_UTIL_SELECT:
			fc_PortalRunUtility(fc_portal, linitial_node(PlannedStmt, fc_portal->stmts),
							 fc_isTopLevel, true, fc_treceiver, &fc_qc);
			break;

		default:
			elog(ERROR, "unsupported portal strategy: %d",
				 (int) fc_portal->strategy);
			break;
	}

	/* 用实际命令结果覆盖门户完成数据 */
	if (fc_qc.commandTag != CMDTAG_UNKNOWN)
		CopyQueryCompletion(&fc_portal->qc, &fc_qc);

	fc_treceiver->rDestroy(fc_treceiver);
}

/*
 * RunFromStore
 *		从门户的元组存储中提取元组。
 *
 * 调用约定类似于 ExecutorRun，除了我们
 * 不依赖于有 queryDesc 或 estate。因此，我们将处理的元组数量作为结果返回，而不是在 estate->es_processed 中。
 *
 * 与 ExecutorRun 的一个不同之处在于，目标接收器函数
 * 在调用者的内存上下文中运行（由于我们没有 estate）。注意可能的内存泄漏。
 */
static uint64 fc_RunFromStore(Portal fc_portal, ScanDirection fc_direction, uint64 fc_count,
			 DestReceiver *fc_dest)
{
	uint64		fc_current_tuple_count = 0;
	TupleTableSlot *fc_slot;

	fc_slot = MakeSingleTupleTableSlot(fc_portal->tupDesc, &TTSOpsMinimalTuple);

	fc_dest->rStartup(fc_dest, CMD_SELECT, fc_portal->tupDesc);

	if (ScanDirectionIsNoMovement(fc_direction))
	{
		/* 除了开始/停止目标外不做其他事情 */
	}
	else
	{
		bool		fc_forward = ScanDirectionIsForward(fc_direction);

		for (;;)
		{
			MemoryContext fc_oldcontext;
			bool		fc_ok;

			fc_oldcontext = MemoryContextSwitchTo(fc_portal->holdContext);

			fc_ok = tuplestore_gettupleslot(fc_portal->holdStore, fc_forward, false,
										 fc_slot);

			MemoryContextSwitchTo(fc_oldcontext);

			if (!fc_ok)
				break;

			/*
			 * 如果我们无法发送元组，我们假设目标已关闭，不能再发送更多
			 * 元组。如果是这样，结束循环。
			 */
			if (!fc_dest->receiveSlot(fc_slot, fc_dest))
				break;

			ExecClearTuple(fc_slot);

			/*
			 * 检查我们的元组数量.. 如果我们已经处理了适当的数量
			 * 那么退出，否则再次循环并处理更多元组。零计数
			 * 意味着没有限制。
			 */
			fc_current_tuple_count++;
			if (fc_count && fc_count == fc_current_tuple_count)
				break;
		}
	}

	fc_dest->rShutdown(fc_dest);

	ExecDropSingleTupleTableSlot(fc_slot);

	return fc_current_tuple_count;
}

/*
 * PortalRunUtility
 *		在门户内执行一个实用语句。
 */
static void fc_PortalRunUtility(Portal fc_portal, PlannedStmt *fc_pstmt,
				 bool fc_isTopLevel, bool fc_setHoldSnapshot,
				 DestReceiver *fc_dest, QueryCompletion *fc_qc)
{
	/*
	 * 如果实用语句需要一个快照，则设置快照。
	 */
	if (PlannedStmtRequiresSnapshot(fc_pstmt))
	{
		Snapshot	fc_snapshot = GetTransactionSnapshot();

		/* 如果有要求，注册我们正在使用的快照并保存在 portal 中 */
		if (fc_setHoldSnapshot)
		{
			fc_snapshot = RegisterSnapshot(fc_snapshot);
			fc_portal->holdSnapshot = fc_snapshot;
		}

		/* 在任何情况下，都要使快照处于活动状态并在 portal 中记住它。因为 portal 现在引用了快照，我们必须告诉 snapmgr.c 该快照属于 portal 的事务级别，否则我们可能会导致 portalSnapshot 成为悬空指针。 */
		PushActiveSnapshotWithLevel(fc_snapshot, fc_portal->createLevel);
		/* PushActiveSnapshotWithLevel 可能已经复制了快照 */
		fc_portal->portalSnapshot = GetActiveSnapshot();
	}
	else
		fc_portal->portalSnapshot = NULL;

	ProcessUtility(fc_pstmt,
				   fc_portal->sourceText,
				   (fc_portal->cplan != NULL), /* 如果在 plancache 中，保护树 */
				   fc_isTopLevel ? PROCESS_UTILITY_TOPLEVEL : PROCESS_UTILITY_QUERY,
				   fc_portal->portalParams,
				   fc_portal->queryEnv,
				   fc_dest,
				   fc_qc);

	/* 一些实用语句可能会更改我们的上下文 */
	MemoryContextSwitchTo(fc_portal->portalContext);

	/* 一些实用命令（例如，VACUUM）会从我们之下弹出 ActiveSnapshot 栈，因此如果现在为空，不要抱怨。否则，我们的快照应该是最上面的；弹出它。请注意，这可能与我们上面创建的快照不同；请参见 EnsurePortalSnapshotExists。 */
	if (fc_portal->portalSnapshot != NULL && ActiveSnapshotSet())
	{
		Assert(fc_portal->portalSnapshot == GetActiveSnapshot());
		PopActiveSnapshot();
	}
	fc_portal->portalSnapshot = NULL;
}

/*
 * PortalRunMulti
 *		执行 portal 的查询，通常情况（多个查询或非 SELECT 类查询）
 */
static void fc_PortalRunMulti(Portal fc_portal,
			   bool fc_isTopLevel, bool fc_setHoldSnapshot,
			   DestReceiver *fc_dest, DestReceiver *fc_altdest,
			   QueryCompletion *fc_qc)
{
	bool		fc_active_snapshot_set = false;
	ListCell   *fc_stmtlist_item;

	/* 如果目标是 DestRemoteExecute，则更改为 DestNone。原因是客户端不会期望任何元组，并且确实无从知道它们是什么，因为在此执行策略下没有规定 Describe 发送 RowDescription 消息。目前，这仅会影响通过重写规则添加到非 SELECT 查询中的 SELECT 命令：这些命令将被执行，但结果将被丢弃，除非您使用“简单查询”协议。 */
	if (fc_dest->mydest == DestRemoteExecute)
		fc_dest = None_Receiver;
	if (fc_altdest->mydest == DestRemoteExecute)
		fc_altdest = None_Receiver;

	/* 循环处理由分析和重写生成的单个查询。 */
	foreach(fc_stmtlist_item, fc_portal->stmts)
	{
		PlannedStmt *fc_pstmt = lfirst_node(PlannedStmt, fc_stmtlist_item);

		/* 如果在前一命令中收到了取消信号，则退出 */
		CHECK_FOR_INTERRUPTS();

		if (fc_pstmt->utilityStmt == NULL)
		{
			/* 处理可规划查询。 */
			TRACE_POSTGRESQL_QUERY_EXECUTE_START();

			if (log_executor_stats)
				ResetUsage();

			/* 可规划查询必须始终有一个快照。第一次时，获取一个新的快照；在同一 portal 的后续查询中，只需更新快照的命令计数器的副本。 */
			if (!fc_active_snapshot_set)
			{
				Snapshot	fc_snapshot = GetTransactionSnapshot();

				/* 如果有要求，注册快照并保存在 portal 中 */
				if (fc_setHoldSnapshot)
				{
					fc_snapshot = RegisterSnapshot(fc_snapshot);
					fc_portal->holdSnapshot = fc_snapshot;
				}

				/* 我们不能让 holdSnapshot 也成为活动的快照，因为 UpdateActiveSnapshotCommandId 会投诉。所以强制进行额外的快照复制。普通的 PushActiveSnapshot 会复制事务快照，因此这仅在 setHoldSnapshot 为真时增加一个复制步骤。（活动快照的命令 ID 与 holdSnapshot 的不同是可以的。） */
				PushCopiedSnapshot(fc_snapshot);

				/* 至于 PORTAL_ONE_SELECT portals，这里似乎没有必要维护 portal->portalSnapshot。 */

				fc_active_snapshot_set = true;
			}
			else
				UpdateActiveSnapshotCommandId();

			if (fc_pstmt->canSetTag)
			{
				/* 语句可以设置标签字符串 */
				fc_ProcessQuery(fc_pstmt,
							 fc_portal->sourceText,
							 fc_portal->portalParams,
							 fc_portal->queryEnv,
							 fc_dest, fc_qc);
			}
			else
			{
				/* 重写添加的语句不能设置标签 */
				fc_ProcessQuery(fc_pstmt,
							 fc_portal->sourceText,
							 fc_portal->portalParams,
							 fc_portal->queryEnv,
							 fc_altdest, NULL);
			}

			if (log_executor_stats)
				ShowUsage("EXECUTOR STATISTICS");

			TRACE_POSTGRESQL_QUERY_EXECUTE_DONE();
		}
		else
		{
			/*
			 * 处理实用函数（创建、销毁等）
			 *
			 * 对于实用命令，我们不能在这里设置快照（如果需要，PortalRunUtility 会处理这个）。如果一个实用命令单独在一个 portal 中，那么一切都很好。唯一允许实用命令成为更长列表的一部分的情况是规则允许包含 NotifyStmt。NotifyStmt 不关心是否有快照，因此如果我们有一个快照，就保持当前快照不变。
			 */
			if (fc_pstmt->canSetTag)
			{
				Assert(!fc_active_snapshot_set);
				/* 语句可以设置标签字符串 */
				fc_PortalRunUtility(fc_portal, fc_pstmt, fc_isTopLevel, false,
								 fc_dest, fc_qc);
			}
			else
			{
				Assert(IsA(fc_pstmt->utilityStmt, NotifyStmt));
				/* 重写添加的语句不能设置标签 */
				fc_PortalRunUtility(fc_portal, fc_pstmt, fc_isTopLevel, false,
								 fc_altdest, NULL);
			}
		}

		/* 清理附带上下文以回收临时内存。 */
		Assert(fc_portal->portalContext == CurrentMemoryContext);

		MemoryContextDeleteChildren(fc_portal->portalContext);

		/* 如果 portal->stmts 已被重置，避免崩溃。这只能发生在 CALL 或 DO 实用语句执行内部 COMMIT/ROLLBACK 时（参见 PortalReleaseCachedPlan）。CALL 或 DO 必须是 portal 中唯一的语句，因此我们没有其他处理工作；但是我们不想解引用现在的悬空列表指针。 */
		if (fc_portal->stmts == NIL)
			break;

		/* 在查询之间增加命令计数器，但在最后一个查询之后不增加。 */
		if (lnext(fc_portal->stmts, fc_stmtlist_item) != NULL)
			CommandCounterIncrement();
	}

	/* 如果我们推送了一个快照，则弹出快照。 */
	if (fc_active_snapshot_set)
		PopActiveSnapshot();

	/* 如果提供了查询完成数据，请使用它。否则使用 portal 的查询完成数据。   异常：客户端期望 INSERT/UPDATE/DELETE 标签有计数，因此用零伪造它们。如果没有与原始相同类型的替代查询，则可以在 DO INSTEAD 规则下发生这种情况。在这里我们打印“0 0”，因为从技术上讲没有匹配标签类型的查询，并且打印不同查询类型的非零计数似乎是错误的，例如，一个 INSERT 做了 UPDATE 不应该打印“0 1”，如果更新了一行。有关详细信息，请参见 QueryRewrite()，第 3 步。 */
	if (fc_qc && fc_qc->commandTag == CMDTAG_UNKNOWN)
	{
		if (fc_portal->qc.commandTag != CMDTAG_UNKNOWN)
			CopyQueryCompletion(fc_qc, &fc_portal->qc);
		/* 如果调用者提供了qc，我们现在应该已经设置好了。 */
		Assert(fc_qc->commandTag != CMDTAG_UNKNOWN);
	}
}

/*
 * PortalRunFetch
 *		PortalRun的变体形式，支持SQL FETCH方向。
 *
 * 注意：我们目前假设没有调用者希望isTopLevel = true。
 *
 * count <= 0被解释为无操作：目标被启动
 * 并关闭，但没有其他操作。此外，count == FETCH_ALL被
 * 解释为“所有行”。（参见FetchStmt.howMany）
 *
 * 返回处理的行数（适合用于结果标签）
 */
uint64 PortalRunFetch(Portal fc_portal,
			   FetchDirection fc_fdirection,
			   long fc_count,
			   DestReceiver *fc_dest)
{
	uint64		fc_result;
	Portal		fc_saveActivePortal;
	ResourceOwner fc_saveResourceOwner;
	MemoryContext fc_savePortalContext;
	MemoryContext fc_oldContext;

	AssertArg(PortalIsValid(fc_portal));

	/*
	 * 检查门户使用不当，并标记门户为活动。
	 */
	MarkPortalActive(fc_portal);

	/*
	 * 设置全局门户上下文指针。
	 */
	fc_saveActivePortal = ActivePortal;
	fc_saveResourceOwner = CurrentResourceOwner;
	fc_savePortalContext = PortalContext;
	PG_TRY();
	{
		ActivePortal = fc_portal;
		if (fc_portal->resowner)
			CurrentResourceOwner = fc_portal->resowner;
		PortalContext = fc_portal->portalContext;

		fc_oldContext = MemoryContextSwitchTo(PortalContext);

		switch (fc_portal->strategy)
		{
			case PORTAL_ONE_SELECT:
				fc_result = fc_DoPortalRunFetch(fc_portal, fc_fdirection, fc_count, fc_dest);
				break;

			case PORTAL_ONE_RETURNING:
			case PORTAL_ONE_MOD_WITH:
			case PORTAL_UTIL_SELECT:

				/*
				 * 如果我们尚未执行命令，则执行它，将结果
				 * 存储在门户的tuplestore中。
				 */
				if (!fc_portal->holdStore)
					fc_FillPortalStore(fc_portal, false /* isTopLevel */ );

				/*
				 * 现在获取所需部分的结果。
				 */
				fc_result = fc_DoPortalRunFetch(fc_portal, fc_fdirection, fc_count, fc_dest);
				break;

			default:
				elog(ERROR, "unsupported portal strategy");
				fc_result = 0;		/* 保持编译器安静 */
				break;
		}
	}
	PG_CATCH();
	{
		/* 执行门户时发生未捕获的错误：将其标记为已死 */
		MarkPortalFailed(fc_portal);

		/* 恢复全局变量并传播错误 */
		ActivePortal = fc_saveActivePortal;
		CurrentResourceOwner = fc_saveResourceOwner;
		PortalContext = fc_savePortalContext;

		PG_RE_THROW();
	}
	PG_END_TRY();

	MemoryContextSwitchTo(fc_oldContext);

	/* 将门户标记为不活动 */
	fc_portal->status = PORTAL_READY;

	ActivePortal = fc_saveActivePortal;
	CurrentResourceOwner = fc_saveResourceOwner;
	PortalContext = fc_savePortalContext;

	return fc_result;
}

/*
 * DoPortalRunFetch
 *		PortalRunFetch的核心 --- 门户上下文已经设置
 *
 * 在这里，count < 0 通常会反转方向。此外，count == FETCH_ALL
 * 被解释为“所有行”。（参见FetchStmt.howMany）
 *
 * 返回处理的行数（适合用于结果标签）
 */
static uint64 fc_DoPortalRunFetch(Portal fc_portal,
				 FetchDirection fc_fdirection,
				 long fc_count,
				 DestReceiver *fc_dest)
{
	bool		fc_forward;

	Assert(fc_portal->strategy == PORTAL_ONE_SELECT ||
		   fc_portal->strategy == PORTAL_ONE_RETURNING ||
		   fc_portal->strategy == PORTAL_ONE_MOD_WITH ||
		   fc_portal->strategy == PORTAL_UTIL_SELECT);

	/*
	 * 注意：对于NO SCROLL游标，我们禁止向后获取（包括重新获取当前行），
	 * 但我们对此解释得很宽松：您可以使用
	 * 任何FetchDirection选项，只要最终结果是向前移动
	 * 至少一行。目前在DoPortalRewind()和PortalRunSelect()中的
	 * forward == false路径中检查NO SCROLL就足够了；但总有一天我们可能更愿意在这里
	 * 显式考虑该限制。
	 */
	switch (fc_fdirection)
	{
		case FETCH_FORWARD:
			if (fc_count < 0)
			{
				fc_fdirection = FETCH_BACKWARD;
				fc_count = -fc_count;
			}
			/* 从switch中掉出以共享代码与FETCH_BACKWARD */
			break;
		case FETCH_BACKWARD:
			if (fc_count < 0)
			{
				fc_fdirection = FETCH_FORWARD;
				fc_count = -fc_count;
			}
			/* 从开关中跳出以与FETCH_FORWARD共享代码 */
			break;
		case FETCH_ABSOLUTE:
			if (fc_count > 0)
			{
				/*
				 * 定义：回到开始，前进count-1行，返回
				 * 下一行（如果有）。
				 *
				 * 实际上，如果目标小于回到开始的中间，则从当前位置扫描更好。
				 *
				 * 此外，如果当前portalPos超出“长整型”的范围，
				 * 则必须采取困难的方法，以避免count
				 * 参数溢出到PortalRunSelect。我们必须排除
				 * LONG_MAX，以避免count看起来像FETCH_ALL。
				 *
				 * 无论如何，我们都安排向前获取目标行。
				 */
				if ((uint64) (fc_count - 1) <= fc_portal->portalPos / 2 ||
					fc_portal->portalPos >= (uint64) LONG_MAX)
				{
					fc_DoPortalRewind(fc_portal);
					if (fc_count > 1)
						fc_PortalRunSelect(fc_portal, true, fc_count - 1,
										None_Receiver);
				}
				else
				{
					long		fc_pos = (long) fc_portal->portalPos;

					if (fc_portal->atEnd)
						fc_pos++;	/* 如果在末尾，则需要额外获取一行 */
					if (fc_count <= fc_pos)
						fc_PortalRunSelect(fc_portal, false, fc_pos - fc_count + 1,
										None_Receiver);
					else if (fc_count > fc_pos + 1)
						fc_PortalRunSelect(fc_portal, true, fc_count - fc_pos - 1,
										None_Receiver);
				}
				return fc_PortalRunSelect(fc_portal, true, 1L, fc_dest);
			}
			else if (fc_count < 0)
			{
				/*
				 * 定义：前进到末尾，向后退abs(count)-1行，
				 * 返回前一行（如果有）。我们可以优化这一点，如果我们
				 * 事先知道结束在哪里，但通常我们不会。
				 * （是否值得考虑count > 查询大小的一半的情况？
				 * 一旦知道大小，我们可以回退……）
				 */
				fc_PortalRunSelect(fc_portal, true, FETCH_ALL, None_Receiver);
				if (fc_count < -1)
					fc_PortalRunSelect(fc_portal, false, -fc_count - 1, None_Receiver);
				return fc_PortalRunSelect(fc_portal, false, 1L, fc_dest);
			}
			else
			{
				/* count == 0 */
				/* 回到开始，返回零行 */
				fc_DoPortalRewind(fc_portal);
				return fc_PortalRunSelect(fc_portal, true, 0L, fc_dest);
			}
			break;
		case FETCH_RELATIVE:
			if (fc_count > 0)
			{
				/*
				 * 定义：前进count-1行，返回下一行（如果有）。
				 */
				if (fc_count > 1)
					fc_PortalRunSelect(fc_portal, true, fc_count - 1, None_Receiver);
				return fc_PortalRunSelect(fc_portal, true, 1L, fc_dest);
			}
			else if (fc_count < 0)
			{
				/*
				 * 定义：向后退abs(count)-1行，返回前一行（如果
				 * 有）。
				 */
				if (fc_count < -1)
					fc_PortalRunSelect(fc_portal, false, -fc_count - 1, None_Receiver);
				return fc_PortalRunSelect(fc_portal, false, 1L, fc_dest);
			}
			else
			{
				/* count == 0 */
				/* 同FETCH FORWARD 0，因此从开关中跳出 */
				fc_fdirection = FETCH_FORWARD;
			}
			break;
		default:
			elog(ERROR, "bogus direction");
			break;
	}

	/*
	 * 如果fdirection == FETCH_FORWARD或FETCH_BACKWARD，并且count
	 * >= 0，进入此处。
	 */
	fc_forward = (fc_fdirection == FETCH_FORWARD);

	/*
	 * 计数为零意味着重新获取当前行（如果有）（根据SQL）
	 */
	if (fc_count == 0)
	{
		bool		fc_on_row;

		/* 我们是否坐在一行上？ */
		fc_on_row = (!fc_portal->atStart && !fc_portal->atEnd);

		if (fc_dest->mydest == DestNone)
		{
			/* MOVE 0根据FETCH 0是否会返回一行返回0/1 */
			return fc_on_row ? 1 : 0;
		}
		else
		{
			/*
			 * 如果我们坐在一行上，向后退一行，以便我们可以重新获取它。
			 * 如果我们没有坐在一行上，我们仍然必须启动和
			 * 关闭执行器，以便目标被正确初始化
			 * 并关闭；因此继续进行。对于PortalRunSelect，
			 * count == 0意味着我们将不检索任何行。
			 */
			if (fc_on_row)
			{
				fc_PortalRunSelect(fc_portal, false, 1L, None_Receiver);
				/* 设置以向前获取一行 */
				fc_count = 1;
				fc_forward = true;
			}
		}
	}

	/*
	 * 将MOVE BACKWARD ALL优化为回退。
	 */
	if (!fc_forward && fc_count == FETCH_ALL && fc_dest->mydest == DestNone)
	{
		uint64		fc_result = fc_portal->portalPos;

		if (fc_result > 0 && !fc_portal->atEnd)
			fc_result--;
		fc_DoPortalRewind(fc_portal);
		return fc_result;
	}

	return fc_PortalRunSelect(fc_portal, fc_forward, fc_count, fc_dest);
}

/*
 * DoPortalRewind - 将Portal回退到起始点
 */
static void fc_DoPortalRewind(Portal fc_portal)
{
	QueryDesc  *fc_queryDesc;

	/*
	 * 如果我们没有前进或尝试前进游标，则不需要任何工作
	 * （在这种情况下，我们不想抛出NO SCROLL错误）。
	 */
	if (fc_portal->atStart && !fc_portal->atEnd)
		return;

	/* 否则，游标必须允许滚动 */
	if (fc_portal->cursorOptions & CURSOR_OPT_NO_SCROLL)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("cursor can only scan forward"),
				 errhint("Declare it with SCROLL option to enable backward scan.")));

	/* 如果我们有holdStore，则回退holdStore */
	if (fc_portal->holdStore)
	{
		MemoryContext fc_oldcontext;

		fc_oldcontext = MemoryContextSwitchTo(fc_portal->holdContext);
		tuplestore_rescan(fc_portal->holdStore);
		MemoryContextSwitchTo(fc_oldcontext);
	}

	/* 如果活动，则回退执行器 */
	fc_queryDesc = fc_portal->queryDesc;
	if (fc_queryDesc)
	{
		PushActiveSnapshot(fc_queryDesc->snapshot);
		ExecutorRewind(fc_queryDesc);
		PopActiveSnapshot();
	}

	fc_portal->atStart = true;
	fc_portal->atEnd = false;
	fc_portal->portalPos = 0;
}

/*
 * PlannedStmtRequiresSnapshot - 它的意思是它所说的
 */
bool PlannedStmtRequiresSnapshot(PlannedStmt *fc_pstmt)
{
	Node	   *fc_utilityStmt = fc_pstmt->utilityStmt;

	/* 如果这不是一个实用语句，它肯定需要一个快照 */
	if (fc_utilityStmt == NULL)
		return true;

	/*
	 * 大多数实用语句需要快照，关于新的快照
	 * 的默认假设应该是它们也需要。因此，枚举那些
	 * 不需要快照的。
	 *
	 * 事务控制、LOCK和SET*不*需要设置快照，因为
	 * 它们需要在不冻结快照的交易快照模式
	 * 事务的开始时可执行。可以拓展允许SHOW
	 * 不设置快照。其他列出的语句只是效率
	 * 修复。要注意列出任何可能修改数据库的内容——如果，
	 * 例如，它必须使用调用用户定义函数的表达式更新索引，
	 * 那么它最好有一个快照。
	 */
	if (IsA(fc_utilityStmt, TransactionStmt) ||
		IsA(fc_utilityStmt, LockStmt) ||
		IsA(fc_utilityStmt, VariableSetStmt) ||
		IsA(fc_utilityStmt, VariableShowStmt) ||
		IsA(fc_utilityStmt, ConstraintsSetStmt) ||
	/* 从这里开始的效率修复 */
		IsA(fc_utilityStmt, FetchStmt) ||
		IsA(fc_utilityStmt, ListenStmt) ||
		IsA(fc_utilityStmt, NotifyStmt) ||
		IsA(fc_utilityStmt, UnlistenStmt) ||
		IsA(fc_utilityStmt, CheckPointStmt))
		return false;

	return true;
}

/*
 * EnsurePortalSnapshotExists - 在需要时重新创建Portal级快照
 *
 * 通常，只要我们在Portal内执行，
 * 就会有一个活动快照，除非Portal的查询是
 * 被豁免的实用语句之一（见PlannedStmtRequiresSnapshot）。
 * 然而，过程和DO块可以提交或中止事务，
 * 从而销毁所有快照。可以调用此函数来
 * 当没有快照时重新建立Portal级快照。
 */
void EnsurePortalSnapshotExists(void)
{
	Portal		fc_portal;

	/*
	 * 如果设置了快照，则无事可做。（我们相信最外层
	 * 的活动快照属于某个Portal；或者如果没有Portal，
	 * 那么管理事情的责任属于其他人。）
	 */
	if (ActiveSnapshotSet())
		return;

	/* 否则，我们最好有一个活动的Portal */
	fc_portal = ActivePortal;
	if (unlikely(fc_portal == NULL))
		elog(ERROR, "cannot execute SQL without an outer snapshot or portal");
	Assert(fc_portal->portalSnapshot == NULL);

	/*
	 * 创建一个新的快照，使其活动，并在Portal中记住它。
	 * 因为Portal现在引用该快照，我们必须告诉snapmgr.c
	 * 快照属于Portal的事务级别，否则我们
	 * 可能会让portalSnapshot成为悬空指针。
	 */
	PushActiveSnapshotWithLevel(GetTransactionSnapshot(), fc_portal->createLevel);
	/* PushActiveSnapshotWithLevel 可能已经复制了快照 */
	fc_portal->portalSnapshot = GetActiveSnapshot();
}
